//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <mantle.h>
#include "peimage.h"
#include "loader.h"

Section::~Section()
{
    if (m_pFile) delete m_pFile;
}


ECode Section::Flush()
{
    m_cache.FreeAllPages();
    return NOERROR;
}

PPage * Section::GetPage(UInt32 uOffset)
{
    PPage *pPage;

    assert(!m_pFile || !m_pFile->HasProxy());

    ForEachDLinkNode(PPage *, pPage, &(m_cache.m_pagesList)) {
        if (FALL_INTO(uOffset, pPage->m_uOffset, pPage->m_uSize)) {
            return pPage;
        }
    }

    PPage * pNewPage;
    UInt32  uByteToRead;
    PVoid   pVirAddr;
    ECode   ec;


    uOffset = AlignPageDown(uOffset);

    pNewPage = m_cache.CreatePage(uOffset, 0);
    if (!pNewPage) {
        return NULL;
    }

    assert(pNewPage->m_uOffset == uOffset);


    pVirAddr = __VA(pNewPage->m_uAddr);
    if (m_uSizeOfRawData > uOffset) {
        uByteToRead = MIN(PAGE_SIZE, m_uSizeOfRawData - uOffset);
    }
    else {
        uByteToRead = 0;
    }

    if (uByteToRead) {
        ec = m_pFile->Read(m_uPointerToRawData + uOffset, uByteToRead, &pVirAddr, FALSE);
        if (FAILED(ec)) {
            pNewPage->Detach();
            DzFreePages(pNewPage->m_uAddr, pNewPage->m_uOrder);
            delete pNewPage;
            return NULL;
        }
    }

    assert(pNewPage->m_uOffset == uOffset);

    if (PAGE_SIZE - uByteToRead) {
        memset((char*)pVirAddr + uByteToRead, '\0', PAGE_SIZE - uByteToRead);
    }

    assert(pNewPage->m_uOffset == uOffset);

    return pNewPage;
}

ECode Section::MemAlloc(Boolean bNeedContMem)
{
    register UInt32 n;

    assert((m_uVSize & ~PAGE_MASK) == 0);

    if (bNeedContMem) {
        n = (m_uVSize >> PAGE_SHIFT);
        n = GET_UP_ORDER(n);
        if (n == (UInt32)-1) return E_OUT_OF_MEMORY;

        if (!m_cache.CreatePage(0, n)) {
            return E_OUT_OF_MEMORY;
        }
    }
    else {
        return m_cache.AllocPages(0, B_TO_P(m_uVSize));
    }

    return NOERROR;
}

Boolean Section::RelocAddr(UInt32 vAddr, UInt32 uOffset, UInt32 uType)
{
    PPage * pPage;
    UInt32 uSecOffset, *pAddr;
    UInt32 *pNextAddr, uTempInt, n;

    assert(FALL_INTO(vAddr, m_uVAddr, m_uVSize));

    uSecOffset = vAddr - m_uVAddr;
    ForEachDLinkNode(PPage *, pPage, &(m_cache.m_pagesList)) {
        if (FALL_INTO(uSecOffset, pPage->m_uOffset, pPage->m_uSize)) {
            pAddr = (UInt32 *)__VA(
                pPage->m_uAddr + uSecOffset - pPage->m_uOffset);
            // Relocation
            Int32     nTemp;

            switch (uType) {
                case IMAGE_REL_BASED_ABSOLUTE:
                    /* Absolute : no fixup required */
                    break;

                case IMAGE_REL_BASED_HIGH:
                case IMAGE_REL_BASED_HIGHADJ:
                    /* High - (16-bits) relocate the high half of an address */
                    nTemp = *((UInt16 *)pAddr) << 16;
                    nTemp += uOffset;
                    *((UInt16 *)pAddr) = (UInt16) (nTemp >> 16);
                    break;

                case IMAGE_REL_BASED_LOW:
                    /* Low - (16-bit) relocate the low half of an address */
                    nTemp = *((Int16*) pAddr);
                    nTemp += uOffset;
                    *((UInt16*) pAddr) = (UInt16) nTemp;
                    break;

                case IMAGE_REL_BASED_HIGHLOW:
                    *pAddr += uOffset;
                    break;

                case IMAGE_REL_BASED_MIPS_JMPADDR:
                    /* JumpAddress - (32-bits) relocate a MIPS jump address */
                    nTemp = (*((PUInt32) pAddr) & 0x3ffffff) << 2;
                    nTemp += uOffset;
                    *((PUInt32) pAddr) = (*((PUInt32) pAddr) & ~0x3ffffff) |
                                        ((nTemp >> 2) & 0x3ffffff);
                    break;

                default:
                    return FALSE;
            }

            *pAddr -= uOffset;

            if (uSecOffset <= pPage->m_uOffset + pPage->m_uSize - 4) {
                *pAddr += uOffset;
            }
            //When the address being relocated is at the edge of page
            else {
                //The virtual address of next physical page
                pNextAddr = (UInt32 *)__VA(((PPage*)pPage->m_pNext)->m_uAddr);

                //get the value needing to be relocated
                n = 0;
                while (n < 4) {
                    if ((int)pAddr & 0x4) {
                        *((byte*)&uTempInt + n) = *(byte*)pAddr;
                        pAddr = (UInt32*)((byte*)pAddr + 1);
                        n++;
                    }
                    else {
                        *((byte*)&uTempInt + n) = *(byte*)pNextAddr;
                        pAddr = (UInt32*)((byte*)pAddr + 1);
                        pNextAddr = (UInt32*)((byte*)pNextAddr + 1);
                        n++;
                    }
                }

                //Relocation
                uTempInt += uOffset;

                //After relocation, write the value
                n = 0;
                pAddr -= 1;
                pNextAddr = (UInt32*)AlignPageDown((UInt32)pNextAddr);
                while (n < 4) {
                    if ((int)pAddr & 0x4) {
                        *(byte*)pAddr = *((byte*)&uTempInt + n);
                        pAddr = (UInt32*)((byte*)pAddr + 1);
                        n++;
                    }
                    else {
                        *(byte*)pNextAddr = *((byte*)&uTempInt + n);
                        pAddr = (UInt32*)((byte*)pAddr + 1);
                        pNextAddr = (UInt32*)((byte*)pNextAddr + 1);
                        n++;
                    }
                }
            }
            return TRUE;
        }
    }
    assert(0);
    return FALSE;
}
